﻿using System;
using PoolKit;
using PoolKit.Extensions;
using PoolKit.Strategies.Creation;

namespace PoolKit
{
    /// <summary>
    /// TODO: consider dumping this one, and going for non starvable resource collection instead (target of opportunity for next releases)
    /// </summary>
    /// <typeparam name="TResource"></typeparam>
    internal class DynamicPool<TResource> : Pool<TResource>
        where TResource : class
    {
        private readonly MutablePool<TResource> _pool;
        private readonly ResourceCreationStrategy<TResource> _resourceCreationStrategy;

        public DynamicPool(
            MutablePool<TResource> underlyingPool,
            ResourceCreationStrategy<TResource> resourceCreationStrategy)
        {
            if (underlyingPool == null) throw new ArgumentNullException("underlyingPool");
            if (resourceCreationStrategy == null) throw new ArgumentNullException("resourceCreationStrategy");

            _pool = underlyingPool;
            _resourceCreationStrategy = resourceCreationStrategy.ToThreadSafe();
        }

        /// <summary>
        /// Asks the pool to lend you a resource
        /// </summary>
        /// <returns>
        /// A resource borrowed from pool
        /// </returns>
        /// <exception cref="CannotLendResourceException"></exception>
        /// <exception cref="ObjectDisposedException"></exception>
        public override TResource Lend()
        {
            TResource resource;
            this.TryLend(out resource);

            return resource;
        }

        /// <summary>
        /// Asks the pool to give up a resource to you - after this the pool no longer tracks the
        /// resource and you cannot return it to the pool
        /// </summary>
        /// <returns>
        /// A resource from pool
        /// </returns>
        /// <exception cref="CannotGiveUpResourceException"></exception>
        /// <exception cref="ObjectDisposedException"></exception>
        public override TResource GiveUp()
        {
            return _pool.GiveUp();
        }

        /// <summary>
        /// Asks the pool to lend you a resource.
        /// </summary>
        /// <param name="resource">A resource borrowed from pool</param>
        /// <returns>True, if the resource has been lent, False - otherwise</returns>
        /// <exception cref="ObjectDisposedException"></exception>
        /// <exception cref="CannotAddResourceException"></exception>
        internal override bool TryLend(out TResource resource)
        {
            bool gotIt = _pool.TryLend(out resource);

            if (!gotIt)
            {
                try
                {
                    resource = _resourceCreationStrategy.Create();
                }
                catch (Exception exception)
                {
                    if (exception.IsNotWrappable())
                    {
                        throw;
                    }

                    throw new CannotLendResourceException(
                        Resources.NonWorkingResourceCreationStrategyDetected
                            .FormatWith(_resourceCreationStrategy), exception);
                }

                if (resource == null)
                {
                    throw new CannotLendResourceException(
                        Resources.ResourceProducingStrategyFailedNullsOrSameThings
                            .FormatWith(_resourceCreationStrategy));
                }

                try
                {
                    _pool.RegisterLentResource(resource);
                }
                catch (CannotAddResourceException exception)
                {
                    throw new CannotLendResourceException(
                        Resources.ResourceProducingStrategyFailedNullsOrSameThings
                            .FormatWith(_resourceCreationStrategy), exception);
                }
            }

            return true;
        }

        /// <summary>
        /// Returns a previously borrowed resource to the pool
        /// </summary>
        /// <param name="resource">A previously borrowed resource to be returned</param>
        /// <exception cref="CannotAcceptResourceException"></exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ObjectDisposedException"></exception>
        public override void AcceptBack(TResource resource)
        {
            _pool.AcceptBack(resource);
        }

        /// <summary>
        /// Returns a previously borrowed, broken resource to the pool
        /// </summary>
        /// <param name="resource">Broken resource to be returned.</param>
        /// <exception cref="ObjectDisposedException"></exception>
        /// <exception cref="CannotAcceptResourceException"></exception>
        /// <exception cref="ArgumentNullException"></exception>
        public override void AcceptBackBroken(TResource resource)
        {
            _pool.AcceptBackBroken(resource);
        }

        /// <summary>
        /// Disposes of the pool
        /// </summary>
        public override void Dispose()
        {
            _pool.Dispose();
        }
    }
}